/*
 * @Author: Clark
 * @Email: haixuanwoTxh@gmail.com
 * @Date: 2023-01-28 14:59:03
 * @LastEditors: Clark
 * @LastEditTime: 2023-01-28 15:46:28
 * @Description: file content
 */
#include "apue.h"
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

extern int makethread(void *(*)(void *), void *);

struct to_info{
    void (*to_fn)(void *);
    void *to_arg;
    struct timespec to_wait;
};

#define SECTONSEC 1000000000

#if !defined(CLOCK_REALTIME) || defined(BSD)
#define clock_nanosleep(ID, FL, REQ, REM) nanosleep((REQ), (REM))
#endif

#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME 0
#define USECTONSEC 1000

void clock_gettime(int id, struct timespec *tsp)
{
    struct timeval tv;

    gettimeofday(&tv, NULL);
    tsp->tv_sec = tv.tv_sec;
    tsp->tv_nsec = tv.tv_usec * USECTONSEC;
}
#endif

void *timeout_helper(void *arg)
{
    printf("timeout_helper\n");
    struct to_info *tip = (struct to_info *)arg;
    clock_nanosleep(CLOCK_REALTIME, 0, &tip->to_wait, NULL);
    (*tip->to_fn)(tip->to_arg);
    free(arg);

    return 0;
}

void timeout(const struct timespec *when, void (*func)(void *), void *arg)
{
    struct timespec now;
    struct to_info *tip;
    int err;

    printf("in timeout func\n");

    clock_gettime(CLOCK_REALTIME, &now);
    if ((when->tv_sec > now.tv_sec) ||
        (when->tv_sec == now.tv_sec && when->tv_nsec > now.tv_nsec))
    {
        tip = malloc(sizeof(struct to_info));
        if (NULL != tip)
        {
            tip->to_fn = func;
            tip->to_arg = arg;
            tip->to_wait.tv_sec = when->tv_sec - now.tv_sec;

            if (when->tv_nsec >= now.tv_nsec)
            {
                tip->to_wait.tv_nsec = when->tv_nsec - now.tv_nsec;
            }
            else
            {
                tip->to_wait.tv_sec--;
                tip->to_wait.tv_nsec = SECTONSEC - now.tv_nsec + when->tv_nsec;
            }

            err = makethread(timeout_helper, (void *)tip);
            if (0 == err)
            {
                printf("makethread success\n");
                return;
            }
            else
            {
                free(tip);
            }
        }
    }

    // we get here if a when <= now, or (b) malloc fails, or
    // (c) we can't make a thread, so we just call the function now.
    (*func)(arg);
}

pthread_mutexattr_t attr;
pthread_mutex_t mutex;

void retry(void *arg)
{
    pthread_mutex_lock(&mutex);
    // perform retry steps
    printf("retry...\n");
    pthread_mutex_unlock(&mutex);
}

int main(void)
{
    int err, condition, arg;
    struct timespec when;

    if ((err = pthread_mutexattr_init(&attr)) != 0)
    {
        err_exit(err, "pthread_mutexarrt_init failed");
    }

    if ((err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0)
    {
        err_exit(err, "can't set recursive type");
    }

    if ((err = pthread_mutex_init(&mutex, &attr)) != 0)
    {
        err_exit(err, "can't create recursive mutex");
    }

    // continue processing ...
    pthread_mutex_lock(&mutex);

    // check the condition under the protection of a lock to
    // make the check and the call to timeout atomic
    condition = 1;
    if (condition)
    {
        // Calculate the absolute time when we want to retry
        clock_gettime(CLOCK_REALTIME, &when);
        when.tv_sec +=10;
        timeout(&when, retry, (void *)((unsigned long)arg));
    }

    pthread_mutex_unlock(&mutex);
    // continue processing ...

    sleep(3);

    exit(0);
}
